#region Copyright
// 
// Copyright (C) 2008 VirtualStaticVoid <virtualstaticvoid@gmail.com>
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
#endregion

using System;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;

namespace NAom.Core
{
  
  internal static class PropertyAccessorBinder
  {

    public static void BindPropertyAccessors(Type baseType, Type generatedType, FieldBuilderMapping[] fieldBuilderMappings)
    {
      foreach (FieldBuilderMapping fieldBuilderMapping in fieldBuilderMappings)
      {

        object propertyAccessor = PropertyAccessorBinder.CreatePropertyAccessor
          (
            baseType,
            generatedType,
            fieldBuilderMapping.PropertyDataType,
            fieldBuilderMapping.PropertyName,
            /* declaredOnly */ (fieldBuilderMapping.FieldBuilder != null)
          );

        fieldBuilderMapping.PropertyType.BindPropertyAccessor(propertyAccessor);

      }
    }

    private static readonly Type GENERICPROPERTYACCESSORTYPE = typeof(PropertyAccessor<,>);
    private static readonly Type GENERICVALUEGETTERTYPE = typeof(ValueGetter<,>);
    private static readonly Type GENERICVALUESETTERTYPE = typeof(ValueSetter<,>);

    private static object CreatePropertyAccessor(Type baseType, Type generatedType, Type propertyDataType, string propertyName, bool declaredOnly)
    {

      Type propertyAccessorType = GENERICPROPERTYACCESSORTYPE.MakeGenericType(baseType, propertyDataType);
      Type valueGetterType = GENERICVALUEGETTERTYPE.MakeGenericType(baseType, propertyDataType);
      Type valueSetterType = GENERICVALUESETTERTYPE.MakeGenericType(baseType, propertyDataType);

      BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance;

      if (declaredOnly)
        bindingFlags |= BindingFlags.DeclaredOnly;
      // else, it's a property that was already implemented on the base class!

      PropertyInfo propertyInfo = generatedType.GetProperty(propertyName, bindingFlags);
      Debug.Assert(propertyInfo != null);

      MethodInfo propertyGetterMethod = propertyInfo.GetGetMethod();
      MethodInfo propertySetterMethod = propertyInfo.GetSetMethod();

      Debug.Assert(propertyGetterMethod != null);
      Debug.Assert(propertySetterMethod != null);

      // create dynamic method to provide plumbing between the property and accessor delegates

      DynamicMethod getterMethod = GenerateGetterMethod(baseType, generatedType, propertyGetterMethod, propertyDataType, propertyName);
      DynamicMethod setterMethod = GenerateSetterMethod(baseType, generatedType, propertySetterMethod, propertyDataType, propertyName);

      Delegate valueGetter = getterMethod.CreateDelegate(valueGetterType);
      Delegate valueSetter = setterMethod.CreateDelegate(valueSetterType);

      return Activator.CreateInstance(propertyAccessorType, valueGetter, valueSetter);

    }

    //
    // Kudo's to "The Code Slinger" for the idea behind this technique!
    //  http://thecodeslinger.wordpress.com/2007/12/03/dynamic-object-instantiation-performance-part-ii/
    //

    private static DynamicMethod GenerateGetterMethod(Type baseType, Type generatedType, MethodInfo propertyGetterMethod, Type propertyDataType, string propertyName)
    {
      if (baseType == null) throw new ArgumentNullException("baseType");
      if (generatedType == null) throw new ArgumentNullException("generatedType");
      if (propertyGetterMethod == null) throw new ArgumentNullException("propertyGetterMethod");

      //
      //  static TValue _get_YYYY_XXXXX_<TTarget, TValue>(TBaseType instance)
      //  {
      //    TTarget target = (TTarget)instance
      //    return target.PropertyXXXXX;   
      //  ]
      //

      DynamicMethod getterMethod = new DynamicMethod
        (
          String.Concat("_get_", generatedType.Name, "_", propertyName, "_"),
          propertyDataType,
          new Type[] { baseType },
          generatedType
        );
      
      ILGenerator getterMethodILGen = getterMethod.GetILGenerator();

      getterMethodILGen.DeclareLocal(generatedType);         // loc0
      getterMethodILGen.DeclareLocal(propertyDataType);      // loc1

      getterMethodILGen.Emit(OpCodes.Ldarg_0);
      getterMethodILGen.Emit(OpCodes.Castclass, generatedType);
      getterMethodILGen.Emit(OpCodes.Stloc_0);

      getterMethodILGen.Emit(OpCodes.Ldloc_0);
      getterMethodILGen.Emit(OpCodes.Callvirt, propertyGetterMethod);
      getterMethodILGen.Emit(OpCodes.Stloc_1);

      getterMethodILGen.Emit(OpCodes.Ldloc_1);
      getterMethodILGen.Emit(OpCodes.Ret);

      return getterMethod;
    }

    private static DynamicMethod GenerateSetterMethod(Type baseType, Type generatedType, MethodInfo propertySetterMethod, Type propertyDataType, string propertyName)
    {
      if (baseType == null) throw new ArgumentNullException("baseType");
      if (generatedType == null) throw new ArgumentNullException("generatedType");
      if (propertySetterMethod == null) throw new ArgumentNullException("propertySetterMethod");

      //
      //  static void _set_YYYY_XXXXX_<TTarget, TValue>(TBaseType instance, TValue value)
      //  {
      //    TTarget target = (TTarget)instance;
      //    target.PropertyXXXXX = value;   
      //  ]
      //

      DynamicMethod setterMethod = new DynamicMethod
        (
          String.Concat("_set_", generatedType.Name, "_", propertyName, "_"),
          typeof(void),
          new Type[] { baseType, propertyDataType},
          generatedType
        );
      
      ILGenerator setterMethodILGen = setterMethod.GetILGenerator();

      setterMethodILGen.DeclareLocal(generatedType);        // loc0
      
      setterMethodILGen.Emit(OpCodes.Ldarg_0);
      setterMethodILGen.Emit(OpCodes.Castclass, generatedType);
      setterMethodILGen.Emit(OpCodes.Stloc_0);
      
      setterMethodILGen.Emit(OpCodes.Ldloc_0);
      setterMethodILGen.Emit(OpCodes.Ldarg_1);
      setterMethodILGen.Emit(OpCodes.Callvirt, propertySetterMethod);

      setterMethodILGen.Emit(OpCodes.Nop);
      setterMethodILGen.Emit(OpCodes.Ret);

      return setterMethod;
    }

  }

}
